Skip to content

fix: save binary fetch responses to a file instead of corrupting them as text#62

Merged
rolznz merged 3 commits into
masterfrom
fix/binary-fetch-output
Jul 24, 2026
Merged

fix: save binary fetch responses to a file instead of corrupting them as text#62
rolznz merged 3 commits into
masterfrom
fix/binary-fetch-output

Conversation

@rolznz

@rolznz rolznz commented Jul 24, 2026

Copy link
Copy Markdown
Member

Fixes #61

fetch read every successful response body with response.text(), which lossily UTF-8-decodes binary bodies (every invalid byte sequence becomes U+FFFD) and then returned the mangled string as JSON content. A paid WAV response arrived as ~50% replacement characters with no way to recover the original bytes - and no error, since the payment succeeded and the status was 200.

Changes

  • Read the body as bytes (arrayBuffer()) and decide text vs binary:
    • a declared text content type (text/*, JSON/XML, form-urlencoded) is returned inline as content, as before
    • otherwise the bytes qualify as text only when they strictly decode as UTF-8 (covers JSON APIs that omit or mislabel the content type)
    • anything else is binary: it is written to a temp file named with an extension derived from the content type subtype (audio/wav -> .wav, unknown -> .bin), and the output carries outputPath, contentType and sizeBytes instead of content
  • New -o, --output <path> flag: saves any response body (text included) verbatim to the given path and returns the path, so an agent expecting a large output never round-trips it through stdout
  • Non-OK responses are unchanged (error bodies stay text), as is the payment object in all cases

A file was chosen over base64-in-JSON: the consumer almost always needs the binary as a file anyway (play it, upload it, feed it to ffmpeg), and base64 would add a manual decode step plus ~1.3x payload inflation.

Verification

  • 5 new tests in src/test/fetch-binary.test.ts (binary saved byte-for-byte, .bin fallback, declared-text stays inline, valid-UTF-8-without-content-type stays inline, -o forces a file); full suite passes
  • Verified end-to-end with a real 2-sat payment to https://voice.forgemesh.io/v1/tts/base through the l402.space bridge: the response is now saved as a playable .wav whose size matches its RIFF header

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added -o, --output <path> to save fetched responses to a chosen file.
    • Binary responses are automatically saved to temporary files with suitable extensions.
    • Text responses continue to display inline, while saved results report file type and size.
  • Bug Fixes

    • Improved handling of binary, invalid UTF-8, and unlabeled response data.
    • Enhanced fetch errors with response details and payment recovery information.

… as text

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@rolznz, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 28 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 0b3e1b9d-5b12-4b5e-9674-af3528e6d18a

📥 Commits

Reviewing files that changed from the base of the PR and between b5c0d86 and 59aa654.

📒 Files selected for processing (4)
  • package.json
  • src/index.ts
  • src/test/fetch-binary.test.ts
  • src/tools/lightning/fetch.ts
📝 Walkthrough

Walkthrough

The fetch command adds --output. fetch402 now distinguishes text and binary responses, writes binary or forced-output bodies to files, returns typed metadata, and preserves payment details on errors. Tests cover content detection, file output, UTF-8 handling, and cleanup.

Changes

Fetch response output

Layer / File(s) Summary
Typed response processing
src/tools/lightning/fetch.ts
fetch402 returns inline text for eligible responses and writes binary or explicitly requested output to files with content type and size metadata. Non-OK errors include response text and payment recovery details.
CLI output option
src/commands/fetch.ts
The -o, --output option is documented and forwarded to fetch402.
Response output tests
src/test/fetch-binary.test.ts
Tests cover labeled and unlabeled binary responses, text decoding, explicit output paths, metadata, and temporary-file cleanup.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant FetchCommand
  participant Fetch402
  participant FileSystem
  User->>FetchCommand: Run fetch with optional --output
  FetchCommand->>Fetch402: Pass request and outputPath
  Fetch402->>Fetch402: Read bytes and classify response
  Fetch402->>FileSystem: Write binary or forced output
  FileSystem-->>Fetch402: Return output path and size
  Fetch402-->>FetchCommand: Return content or file metadata
  FetchCommand-->>User: Render fetch result
Loading

Possibly related PRs

  • getAlby/cli#7: Overlaps in fetch CLI wiring and fetch402 signatures.
  • getAlby/cli#56: Overlaps in fetch command options and fetch402 behavior.
  • getAlby/cli#59: Overlaps in fetch402 error metadata and CLI option forwarding.

Suggested reviewers: reneaaron

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: preserving binary fetch responses by saving them to a file.
Linked Issues check ✅ Passed The changes address the linked issue by reading bytes, preserving text, and writing binary responses to files with metadata.
Out of Scope Changes check ✅ Passed The added output option and error-detail improvements still support the fetch response handling work and do not appear unrelated.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/binary-fetch-output

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/test/fetch-binary.test.ts`:
- Around line 75-76: Update the test case for the clean UTF-8 inline response to
stub a response without any Content-Type header, ensuring it exercises the
contentType === null path rather than application/octet-stream. Preserve the
existing valid UTF-8 body and inline-return assertions.

In `@src/tools/lightning/fetch.ts`:
- Around line 141-145: Update the response-saving flow around writeFileSync in
fetch so write failures are caught instead of propagated directly. When payment
credentials exist, throw a DetailedError that includes
paidRecoveryDetails(payment); preserve the existing write behavior and error
handling for responses without credentials.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: d9eca5b6-63fe-4d9e-b1b3-e0b692c071a8

📥 Commits

Reviewing files that changed from the base of the PR and between 04e2137 and b5c0d86.

📒 Files selected for processing (3)
  • src/commands/fetch.ts
  • src/test/fetch-binary.test.ts
  • src/tools/lightning/fetch.ts

Comment thread src/test/fetch-binary.test.ts Outdated
Comment thread src/tools/lightning/fetch.ts
rolznz and others added 2 commits July 24, 2026 13:42
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@rolznz
rolznz merged commit ce434db into master Jul 24, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fetch corrupts binary responses by decoding them as UTF-8 text

1 participant